Inspector のコンテナイメージスキャンを CodeBuild で実行してみた #AWSreInvent
こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。
re:Invent 2023 にて、 Amazon Inspector が CI/CD 内で実行可能になりました。
そこで今回は、 CodeBuild のビルド処理中に、 Inspector のコンテナイメージスキャンを試してみようと思います。
やってみる前に
まず、今回発表された Inspector の脆弱性スキャンは、従来提供されているようなコンテナイメージに対して直接スキャンするのではなく、 SBOM(ソフトウェア部品表)ファイルに対して、脆弱性スキャンをかけるような仕組みになります。そのため、脆弱性スキャンを実行する前に SBOM Generator で SBOM ファイルの生成が必要になります。
今回の機能に対応するために、 AWS から Amazon Inspector SBOM Generator (inspector-sbomgen
) が提供開始されました。
Amazon Inspector SBOM Generator
Amazon Inspector SBOM Generator
Amazon Inspector SBOM Generator は先ほども記載とおり、 Inspector-Scan の ScanSbom
API で必要な SBOM ファイルの生成ツールになります。
今回の ScanSbom
API では、 CycloneDX v1.5 形式の SBOM ファイルが必要となり、 Amazon Inspector SBOM Generator は CycloneDX v1.5 形式での SBOM 生成をサポートしています。
何が言いたいかというと、 re:Inforce 2023 で発表された SBOM のエクスポート(CreateSbomExport
) API は 執筆時点で、 CycloneDX v1.5 に対応していないため、注意が必要です。(SBOM 生成ツールに、こだわりがなければ、 Amazon Inspector SBOM Generator を使っていただくのが無難かと思います。)
いくつか気になる要件があるため、ピックアップしてみます。
性能
パフォーマンスを最大限に活かすため、推奨されている CPU, メモリの記載があります。ちょっと大きいなぁと思った方は、ステージ分けるのも、アリだと思います。
For best performance, we recommend running the binary from a system with these minimum hardware specs:
- 4x core CPU
- 8 GB RAM
OS
現時点で、サポートされている OS は、 現状 Linux のみとなります。また、コンテナイメージを扱うため、ビルドされているマシンに Docker, Podman, containerd などがインストールされている必要があります。
特権モードが必要かどうか
Amazon Inspector SBOM Generator は、コンテナイメージのビルドや、アクセス権限の昇格を利用しないため、特権モードは必要ないです。
ただし、シフトレフトな考え方だとコンテナリポジトリへのプッシュ前、つまり、ローカルでコンテナイメージをスキャンするのがより好ましいため、同一ビルド処理内で組むのもありかと思います。
以下はビルド処理の一例です。
- コンテナイメージのビルド
- ローカルでビルドしたイメージをスキャン
- 緊急度に応じた脆弱性があればビルドの停止
- なければ、リポジトリへプッシュ
と言ったものの、緊急度に応じた脆弱性の中でも、すぐに対応が難しいケースもあると思うので、スキャンとプッシュの前後関係は必要に応じて入れ替えていただくといいと思います。
大切なのは、デプロイ前に脆弱性の有無を把握し、検出された脆弱性をどう扱うのかだと私は思います。
コンテナイメージ
コンテナイメージに対してもリミットがあります。
- イメージサイズが 5 GB を超える場合
- レイヤー数が 60 を超える場合
- インストールされているパッケージ数が 2,000 を超える場合
Sbomgen can’t scan container images if they are greater than 5 GB in size, have more than 60 layers, or over 2,000 installed packages.
やってみる
長くなりましたが、それでは CodeBuild 内で、 Inspector のコンテナイメージスキャンを組み込んでみようと思います。
作成する構成は以下の通りです。今回は ECR へプッシュした後に、プッシュしたイメージをスキャンしてみようと思います。
最後に承認ステージを挟んでスキャン結果を確認してみます。
今回のデプロイは、ほぼほぼ Terraform で担います。 terraform apply
中に、イメージスキャンに関して、設定した部分をお読みいただけると幸いです。
最終的なコードは以下になります。
IAM
今回は、 ECR にプッシュされたイメージに対して、 SBOM の生成を行うため、 ECR の権限が必要になります。また、 Inspector-Scan API は従来の、Inspector v2 API とは別のスキーマ(inspector-scan
)で API 提供されいてるため注意です。What's new で紹介されていた、「Inspector を有効化することなく利用可能」の意味は、この部分を指しているのだと思います。
私が今回、イメージスキャン用の CodeBuild に設定した IAM Policy は以下になります。ワイルドカード部分は、もっと絞ることが可能ですが、本筋ではないため省略しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Artfact",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Sid": "BuildLog",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Sid": "PackageVulnerabilityScanning",
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:BatchGetRepositoryScanningConfiguration",
"ecr:GetAuthorizationToken",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRegistryScanningConfiguration",
"inspector-scan:ScanSbom"
],
"Resource": "*"
}
]
}
イメージタグの受け渡し
イメージスキャンを行うステージに、「どのイメージが直近のビルドしたイメージなのか?」、情報を受け渡す必要があります。
今回は、 CodePipeline で exported-variables を利用して、受け渡しを行いました。
そのためには、イメージビルド側の buildspec で、イメージ URI のエクスポートを行います。
version: 0.2
env:
variables:
DOCKER_BUILDKIT: '1'
AWS_PAGER: ''
exported-variables:
- IMAGE_URL
phases:
pre_build:
commands:
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-10)
- IMAGE_TAG=${COMMIT_HASH:=latest}
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG codebuild
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- IMAGE_URL=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
エクスポートした、IMAGE_URL
の受け渡しは以下の部分で行いました。
#################################################
# CodePipeline
#################################################
resource "aws_codepipeline" "main" {
name = "${local.prefix}-pipeline"
role_arn = aws_iam_role.codepipeline.arn
artifact_store {
location = aws_s3_bucket.artifact.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = 1
output_artifacts = ["source_output"]
configuration = {
RepositoryName = aws_codecommit_repository.main.repository_name
BranchName = "main"
OutputArtifactFormat = "CODE_ZIP"
PollForSourceChanges = "false"
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
namespace = "Build"
owner = "AWS"
provider = "CodeBuild"
version = 1
input_artifacts = ["source_output"]
output_artifacts = ["build_output"]
configuration = {
ProjectName = aws_codebuild_project.image_build.name
}
}
}
stage {
name = "Scan"
action {
name = "Scan"
category = "Build"
namespace = "Scan"
owner = "AWS"
provider = "CodeBuild"
version = 1
input_artifacts = ["source_output"]
output_artifacts = ["scan_output"]
configuration = {
ProjectName = aws_codebuild_project.image_scan.name
EnvironmentVariables = jsonencode([
{
name = "IMAGE_URL"
value = "#{Build.IMAGE_URL}"
type = "PLAINTEXT"
}
])
}
}
}
stage {
name = "Approval"
action {
name = "Approval"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = 1
configuration = {
CustomData = "Container image scan result."
ExternalEntityLink = "#{Scan.BUILD_URL}"
}
}
}
}
AWS CLI
AWS CodeBuild で利用されていた、 マネージド型イメージ (public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:5.0-23.07.28) の AWS CLI バージョンは、 2.13.35 でした。
[Container] 2023/12/08 15:25:54.940259 Waiting for agent ping
[Container] 2023/12/08 15:25:55.941284 Waiting for DOWNLOAD_SOURCE
[Container] 2023/12/08 15:25:58.048304 Phase is DOWNLOAD_SOURCE
[Container] 2023/12/08 15:25:58.051089 CODEBUILD_SRC_DIR=/codebuild/output/src1876042495/src
[Container] 2023/12/08 15:25:58.051520 YAML location is /codebuild/output/src1876042495/src/image_scan.yaml
[Container] 2023/12/08 15:25:58.052224 Selecting shell bash as specified in buildspec.
[Container] 2023/12/08 15:25:58.053622 Setting HTTP client timeout to higher timeout for S3 source
[Container] 2023/12/08 15:25:58.053708 Processing environment variables
[Container] 2023/12/08 15:25:58.264585 No runtime version selected in buildspec.
[Container] 2023/12/08 15:25:58.305904 Moving to directory /codebuild/output/src1876042495/src
[Container] 2023/12/08 15:25:58.308790 Unable to initialize cache download: no paths specified to be cached
[Container] 2023/12/08 15:25:58.490902 Configuring ssm agent with target id: codebuild:f2ea7dbb-99c6-4369-81ab-444d04f4a64d
[Container] 2023/12/08 15:25:58.534162 Successfully updated ssm agent configuration
[Container] 2023/12/08 15:25:58.534637 Registering with agent
[Container] 2023/12/08 15:25:58.534656 Phases found in YAML: 4
[Container] 2023/12/08 15:25:58.534677 INSTALL: 12 commands
[Container] 2023/12/08 15:25:58.534682 PRE_BUILD: 2 commands
[Container] 2023/12/08 15:25:58.534686 BUILD: 2 commands
[Container] 2023/12/08 15:25:58.534696 POST_BUILD: 1 commands
[Container] 2023/12/08 15:25:58.535047 Phase complete: DOWNLOAD_SOURCE State: SUCCEEDED
[Container] 2023/12/08 15:25:58.535058 Phase context status code: Message:
[Container] 2023/12/08 15:25:58.637017 Entering phase INSTALL
[Container] 2023/12/08 15:25:58.637526 Running command aws --version
aws-cli/2.13.35 Python/3.11.6 Linux/4.14.291-218.527.amzn2.x86_64 exec-env/AWS_ECS_EC2 exe/x86_64.amzn.2023 prompt/off
inspector-scan
API は、バージョン 2.13.38 から利用可能なため、現時点ではアップデートしてあげる必要があります。
version: 0.2
env:
shell: bash
variables:
DOCKER_BUILDKIT: '1'
AWS_PAGER: ''
exported-variables:
- BUILD_URL
phases:
install:
commands:
- aws --version
- echo AWS CLI update...
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip awscliv2.zip
- ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
- aws --version
- echo Install Amazon Inspector SBOM Generator...
- curl -O https://amazon-inspector-sbomgen.s3.amazonaws.com/latest/linux/amd64/inspector-sbomgen.zip
- unzip inspector-sbomgen.zip
- mv inspector-sbomgen-* inspector-sbomgen-latest
- chmod +x inspector-sbomgen-latest/linux/amd64/inspector-sbomgen
- ./inspector-sbomgen-latest/linux/amd64/inspector-sbomgen --version
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- ./inspector-sbomgen-latest/linux/amd64/inspector-sbomgen container --image $IMAGE_URL --outfile /tmp/sbom.json --quiet
- aws inspector-scan scan-sbom --sbom file:///tmp/sbom.json --output-format INSPECTOR --query 'sbom.vulnerabilities'
post_build:
commands:
- export BUILD_URL=$CODEBUILD_BUILD_URL
コードのプッシュ
そろそろ、 Terraform での AWS インフラができ上がったのではないでしょうか。 ここからは、 Dockerfile や buildspec の配置を行なっていきます。
CodeCommit に送信したいアセットは、 codebuild
フォルダ一式になります。
takakuni@inspector % tree . -L 1
.
├── README.md
├── codebuild
├── codecommit.tf
├── codepipeline.tf
├── ecr.tf
├── event_pattern
├── image_build.tf
├── image_scan.tf
├── local.tf
├── policy_document
├── providers.tf
├── terraform.tfstate
└── terraform.tfstate.backup
CodeCommit に一式コピーする形で、 git push
を行います。
cd ..
git clone ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/inspector-scan-repo
cp -R inspector-cicd-codebuild/codebuild inspector-scan-repo
cd inspector-scan-repo
git add . && git commit -m '[add] code asset added' && git push
挙動を見てみる
API output formats に記載の通り、inspector-scan
API では、 SBOM をもとに脆弱性のある OSS ライブラリの検出を行ってくれます。
{
"status": "SBOM parsed successfully, 1 vulnerability found",
"inspector": {
"messages": [
{
"name": "foo",
"purl": "pkg:maven/foo@1.0.0", // Will not exist in output if missing in sbom
"info": "Component skipped: no rules found."
}
],
"vulnerability_count": {
"critical": 1,
"high": 0,
"medium": 0,
"low": 0
},
"vulnerabilities": [
{
"id": "CVE-2021-44228",
"severity": "critical",
"source": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228",
"related": [
"SNYK-JAVA-ORGAPACHELOGGINGLOG4J-2314720",
"GHSA-jfh8-c2jp-5v3q"
],
"description": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.",
"references": [
"https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00646.html",
"https://support.apple.com/kb/HT213189",
"https://msrc-blog.microsoft.com/2021/12/11/microsofts-response-to-cve-2021-44228-apache-log4j2/",
"https://logging.apache.org/log4j/2.x/security.html",
"https://www.debian.org/security/2021/dsa-5020",
"https://cert-portal.siemens.com/productcert/pdf/ssa-479842.pdf",
"https://www.oracle.com/security-alerts/alert-cve-2021-44228.html",
"https://www.oracle.com/security-alerts/cpujan2022.html",
"https://cert-portal.siemens.com/productcert/pdf/ssa-714170.pdf",
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/M5CSVUNV4HWZZXGOKNSK6L7RPM7BOKIB/",
"https://cert-portal.siemens.com/productcert/pdf/ssa-397453.pdf",
"https://cert-portal.siemens.com/productcert/pdf/ssa-661247.pdf",
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VU57UJDCFIASIO35GC55JMKSRXJMCDFM/",
"https://www.oracle.com/security-alerts/cpuapr2022.html",
"https://twitter.com/kurtseifried/status/1469345530182455296",
"https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-apache-log4j-qRuKNEbd",
"https://lists.debian.org/debian-lts-announce/2021/12/msg00007.html",
"https://www.kb.cert.org/vuls/id/930724"
],
"created": "2021-12-10T10:15:00Z",
"updated": "2023-04-03T20:15:00Z",
"properties": {
"cisa_kev_date_added": "2021-12-10T00:00:00Z",
"cisa_kev_date_due": "2021-12-24T00:00:00Z",
"cwes": [400, 20, 502],
"cvss": [
{
"source": "NVD",
"severity": "critical",
"cvss3_base_score": 10.0,
"cvss3_base_vector": "AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
"cvss2_base_score": 9.3,
"cvss2_base_vector": "AC:M/Au:N/C:C/I:C/A:C"
},
{
"source": "SNYK",
"severity": "critical",
"cvss3_base_score": 10.0,
"cvss3_base_vector": "AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H/E:H"
},
{
"source": "GITHUB",
"severity": "critical",
"cvss3_base_score": 10.0,
"cvss3_base_vector": "AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
}
],
"epss": 0.97565,
"exploit_available": true,
"exploit_last_seen_in_public": "2023-03-06T00:00:00Z"
},
"affects": [
{
"installed_version": "pkg:maven/org.apache.logging.log4j/log4j-core@2.12.1",
"fixed_version": "2.15.0",
"path": "/home/dev/foo.jar"
}
]
}
]
}
}
今回、 Scan で利用した buildspec は、 vulnerabilities だけ抽出するような形で出力しています。
version: 0.2
env:
shell: bash
variables:
DOCKER_BUILDKIT: '1'
AWS_PAGER: ''
exported-variables:
- BUILD_URL
phases:
install:
commands:
- aws --version
- echo AWS CLI update...
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip awscliv2.zip
- ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
- aws --version
- echo Install Amazon Inspector SBOM Generator...
- curl -O https://amazon-inspector-sbomgen.s3.amazonaws.com/latest/linux/amd64/inspector-sbomgen.zip
- unzip inspector-sbomgen.zip
- mv inspector-sbomgen-* inspector-sbomgen-latest
- chmod +x inspector-sbomgen-latest/linux/amd64/inspector-sbomgen
- ./inspector-sbomgen-latest/linux/amd64/inspector-sbomgen --version
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- ./inspector-sbomgen-latest/linux/amd64/inspector-sbomgen container --image $IMAGE_URL --outfile /tmp/sbom.json --quiet
- aws inspector-scan scan-sbom --sbom file:///tmp/sbom.json --output-format INSPECTOR --query 'sbom.vulnerabilities'
post_build:
commands:
- export BUILD_URL=$CODEBUILD_BUILD_URL
CodeBuild 側でも、検出できていることがわかります。
書き方によっては、以下のように重要度が Critical, High の時のみ出力するようなこともできます。
version: 0.2
# 省略
build:
commands:
- ./inspector-sbomgen-latest/linux/amd64/inspector-sbomgen container --image $IMAGE_URL --outfile /tmp/sbom.json --quiet
# - aws inspector-scan scan-sbom --sbom file:///tmp/sbom.json --output-format INSPECTOR --query 'sbom.vulnerabilities'
# 重要度が critical または high の場合、出力する
- aws inspector-scan scan-sbom --sbom file:///tmp/sbom.json --output-format INSPECTOR --query 'sbom.vulnerabilities[?severity==`critical` || severity==`high`]'
まとめ
以上、「Inspector のコンテナイメージスキャンを CodeBuild で実行してみた」でした。
inspector-sbomgen
の正体と、実際にカスタム CI に組み込むにはどうすればいいかが、よくわかりました。実際に触ってみるのが、わかりやすいですね。
この記事がどなたかの参考になれば幸いです。AWS 事業本部コンサルティング部のたかくに(@takakuni_)でした!